﻿using System;
using System.Data;
using System.Configuration;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Configuration;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Xml;
using System.Xml.Linq;

/// <summary>
/// Uses the SmugMug REST API to query data from SmugMug.
/// </summary>
public class SmugMugService
{
    const String BASE_URI = "http://api.smugmug.com/hack/rest/1.1.1/";

    public String ApiKey { get; private set; }
    public String SessionKey { get; private set; }
    public String LastApiCall { get; private set; }
    public String NickName { get; set; }

    /// <summary>
    /// Constructor.
    /// </summary>
    public SmugMugService()
    {
        this.ApiKey = WebConfigurationManager.AppSettings["SmugMug.ApiKey"];
        if (String.IsNullOrEmpty(this.ApiKey)) throw new ConfigurationErrorsException(
             "Using the default constructor of SmugMugService requires you to " +
             "set the 'SmugMug.ApiKey' AppSetting in web.config.");
        SetSessionKey();
    }

    /// <summary>
    /// Constructor.
    /// </summary>
    /// <param name="apiKey">The API key from SmugMug.</param>
	public SmugMugService(String apiKey)
	{
        this.ApiKey = apiKey;
        SetSessionKey();
	}

    /// <summary>
    /// Constructor.
    /// </summary>
    /// <param name="apiKey">The API key from SmugMug.</param>
    /// <param name="nickName">The SmugMug nickname of the account on whose behalf requests are being made.</param>
    public SmugMugService(String apiKey, String nickName)
    {
        this.ApiKey = apiKey;
        this.NickName = nickName;
        SetSessionKey();
    }

    /// <summary>
    /// Gets a list of Categories for the current user.
    /// </summary>
    /// <returns>A List of Category objects.</returns>
    public List<Category> GetCategories()
    {
        IsNickNameSet();
        return GetCategories(this.NickName);
    }

    private List<Category> GetCategories(String nickname)
    {
        String uri = MakeApiCall(ApiMethod.smugmug_categories_get, "NickName|" + nickname);
        XDocument xml = LoadXDocumentFromUri(uri);
        IEnumerable<Category> linqCollection = from item in xml.Descendants("Category")
                                               orderby item.Element("Title").Value
                                               select new Category
                                               {
                                                   ID = item.Attribute("id").Value,
                                                   Title = item.Element("Title").Value
                                               };
        return new List<Category>(linqCollection);
    }

    public Album GetAlbum(String albumId)
    {
        Album item = GetAlbumFromCache(albumId);
        if (item == null)
        {
            String uri = MakeApiCall(ApiMethod.smugmug_albums_getInfo, "AlbumID|" + albumId);
            XDocument xml = LoadXDocumentFromUri(uri);
            IEnumerable<Album> linq =
                from album in xml.Descendants("Album")
                select new Album
                {
                    ID = album.Attribute("id").Value,
                    CategoryID = album.Element("Category").Attribute("id").Value,
                    SubCategoryID = album.Element("SubCategory").Attribute("id").Value,
                    ImageCount = Int32.Parse(album.Element("ImageCount").Value),
                    Title = album.Element("Title").Value,
                    Description = album.Element("Description").Value,
                    LastUpdated = DateTime.Parse(album.Element("LastUpdated").Value)
                };
            if (new List<Album>(linq).Count != 1) throw new ApplicationException("Did not return 1 element.");
            item = new List<Album>(linq)[0];
            PutAlbumIntoCache(item);
        }
        return item;
    }


    public List<Album> GetAlbums()
    {
        IsNickNameSet();
        return GetAlbums(this.NickName);
    }

    public List<Album> GetAlbums(String nickname)
    {
        String uri = MakeApiCall(ApiMethod.smugmug_albums_get, "NickName|" + nickname);
        XDocument xml = LoadXDocumentFromUri(uri);
        IEnumerable<Album> linqCollection = from item in xml.Descendants("Album")
                             orderby item.Element("Category").Element("Title").Value
                             select new Album
                             {
                                 ID = item.Attribute("id").Value,
                                 Title = item.Element("Title").Value,
                                 CategoryName = item.Element("Category").Element("Title").Value,
                                 CategoryID = item.Element("Category").Attribute("id").Value
                             };
        return new List<Album>(linqCollection);
    }

    public List<Album> FindAlbums(String keyword)
    {
        IsNickNameSet();
        return FindAlbums(this.NickName, keyword);
    }

    public List<Album> FindAlbums(String nickname, String keyword)
    {
        List<Album> all = GetAlbums(nickname);
        IEnumerable<Album> linq =
            from a in all
            where (a.Title.Contains(keyword))
            select a;
        return new List<Album>(linq);
    }

    public List<Album> GetAlbumsByCategory(String categoryId)
    {
        IsNickNameSet();
        return GetAlbumsByCategory(this.NickName, categoryId);
    }
    public List<Album> GetAlbumsByCategory(String nickname, String categoryId)
    {
        List<Album> all = GetAlbums(nickname);
        IEnumerable<Album> linq =
            from a in all
            where (a.CategoryID == categoryId)
            select a ;
        return new List<Album>(linq);
    }

    /// <summary>
    /// Gets a list of categories that are in use by the current user.
    /// </summary>
    /// <returns>A List of Category objects.</returns>
    public List<Category> GetUsedCategories()
    {
        IsNickNameSet();
        return GetUsedCategories(this.NickName);
    }

    private List<Category> GetUsedCategories(String nickname)
    {
        String uri = MakeApiCall(ApiMethod.smugmug_albums_get, "NickName|" + nickname);
        XDocument xml = LoadXDocumentFromUri(uri);
        IEnumerable<Category> linq =
            from item in xml.Descendants("Album")
            group item by new
            {
                Title = item.Element("Category").Element("Title").Value,
                ID = item.Element("Category").Attribute("id").Value
            } into g
            select new Category
            {
                Title = g.Key.Title,
                ID = g.Key.ID,
                AlbumCount = g.Count()
            };
        return new List<Category>(linq);
    }

    private List<String> GetImages(String albumId)
    {
        String uri = MakeApiCall(ApiMethod.smugmug_images_get, "AlbumID|" + albumId);
        XDocument xml = LoadXDocumentFromUri(uri);
        IEnumerable<String> linqCollection = from item in xml.Descendants("Image")
                                             select item.Attribute("id").Value;
        return new List<String>(linqCollection);
    }

    /// <summary>
    /// Gets all of the ImageUrlSets for a given Album for the current user.
    /// </summary>
    /// <param name="albumId">The SmugMug ID of the album.</param>
    /// <returns>A List of ImgeUrlSet objects.</returns>
    public List<ImageUrlSet> GetImageUrlSets(String albumId)
    {
        IsNickNameSet();
        return GetImageUrlSets(albumId, this.NickName);
    }

    private List<ImageUrlSet> GetImageUrlSets(String albumId, String nickname)
    {
        List<ImageUrlSet> list = new List<ImageUrlSet>();
        List<String> ids = GetImages(albumId);
        foreach (String id in ids)
        {
            list.Add(new ImageUrlSet
            {
                ImageId = id,
                OriginalUrl = String.Format("http://{0}.smugmug.com/photos/{1}-O.jpg", nickname, id),
                LargeUrl = String.Format("http://{0}.smugmug.com/photos/{1}-L.jpg", nickname, id),
                MediumUrl = String.Format("http://{0}.smugmug.com/photos/{1}-M.jpg", nickname, id),
                SmallUrl = String.Format("http://{0}.smugmug.com/photos/{1}-S.jpg", nickname, id),
                TinyUrl = String.Format("http://{0}.smugmug.com/photos/{1}-Ti.jpg", nickname, id),
                ThumbUrl = String.Format("http://{0}.smugmug.com/photos/{1}-Th.jpg", nickname, id),
                AlbumUrl = null
            });
        }
        return list;
    }

    /// <summary>
    /// Gets the URLs for a given image.
    /// </summary>
    /// <param name="imageId">The SmugMug ID of the image.</param>
    /// <returns>An ImageUrlSet object.</returns>
    public ImageUrlSet GetImageUrlSet(String imageId)
    {
        String uri = MakeApiCall(ApiMethod.smugmug_images_getURLs, "ImageID|" + imageId);
        XDocument xml = LoadXDocumentFromUri(uri);
        IEnumerable<ImageUrlSet> linqCollection = from item in xml.Descendants("Image")
                             select new ImageUrlSet
                             {
                                 OriginalUrl = item.Element("OriginalURL").Value,
                                 LargeUrl = item.Element("LargeURL").Value,
                                 MediumUrl = item.Element("MediumURL").Value,
                                 SmallUrl = item.Element("SmallURL").Value,
                                 TinyUrl = item.Element("TinyURL").Value,
                                 ThumbUrl = item.Element("ThumbURL").Value,
                                 AlbumUrl = item.Element("AlbumURL").Value
                             };
        return new List<ImageUrlSet>(linqCollection)[0];
    }

    #region [Helper Methods]
    private String MakeApiCall(ApiMethod method, params String[] arguments)
    {
        return MakeApiCall(method.ToString().Replace('_', '.'), arguments);
    }

    private String MakeApiCall(String method, params String[] arguments)
    {
        StringBuilder sb = new StringBuilder();
        sb.Append(BASE_URI);
        sb.Append("?method=");
        sb.Append(method);
        sb.Append("&SessionID=");
        sb.Append(this.SessionKey);
        foreach (String arg in arguments)
        {
            if (!String.IsNullOrEmpty(arg) && (arg.Split('|').Length == 2))
            {
                sb.Append("&");
                sb.Append(arg.Split('|')[0]);
                sb.Append("=");
                sb.Append(arg.Split('|')[1]);
            }
        }
        String rest = sb.ToString();
        System.Web.HttpContext.Current.Trace.Write("SmugMug", rest);
        this.LastApiCall = rest;
        return rest;
    }

    private String Get(String uri)
    {
        String response = null;
        HttpWebRequest req = WebRequest.Create(uri) as HttpWebRequest;
        using (HttpWebResponse resp = req.GetResponse() as HttpWebResponse)
        {
            StreamReader sr = new StreamReader(resp.GetResponseStream());
            response = sr.ReadToEnd();
        }
        return response;
    }

    private XmlDocument GetXml(String uri)
    {
        XmlDocument xdoc = new XmlDocument();
        xdoc.LoadXml(Get(uri));
        return xdoc;
    }

    private void SetSessionKey()
    {
        // Look for the session key in ASP.NET cache. If it is not
        // in the cache, then request from SmugMug, and cache for 10 mintues.
        String cached = System.Web.HttpContext.Current.Cache["SmugMug.SessionKey"] as String;
        if (String.IsNullOrEmpty(cached))
        {
            String uri = MakeApiCall(ApiMethod.smugmug_login_anonymously, "APIKey|" + this.ApiKey);
            this.SessionKey = GetXml(uri).DocumentElement.ChildNodes[1].FirstChild.InnerText;
            System.Web.HttpContext.Current.Cache.Insert("SmugMug.SessionKey", this.SessionKey,
                null, DateTime.UtcNow.AddMinutes(10), System.Web.Caching.Cache.NoSlidingExpiration);
        }
        else { this.SessionKey = cached; }
    }

    private XDocument LoadXDocumentFromUri(String uri)
    {
        // Look for this exact URI in ASP.NET cache. If it is not
        // in the cache, then request from SmugMug, and cache for 2 minutes.
        String cacheKey = "SmugMug.REST." + uri.ToLower();
        XDocument cached = System.Web.HttpContext.Current.Cache[cacheKey] as XDocument;
        if (cached == null)
        {
            XDocument xml = XDocument.Load(uri);
            System.Web.HttpContext.Current.Cache.Insert(cacheKey, xml, null,
                DateTime.UtcNow.AddMinutes(2), System.Web.Caching.Cache.NoSlidingExpiration);
            return xml;
        }
        else { return cached; }
    }

    private void IsNickNameSet()
    {
        if (String.IsNullOrEmpty(this.NickName)) throw new ApplicationException(
             "The nickname has not yet been set.");
    }
    #endregion

    #region [Album Cache]
    private System.Web.Caching.Cache Cache { get { return HttpContext.Current.Cache; } }
    const String ALBUM_PREFIX = "Album.";
    private Album GetAlbumFromCache(String id)
    {
        Album item = Cache[ALBUM_PREFIX + id] as Album;
        return item;
    }
    private void PutAlbumIntoCache(Album item)
    {
        Cache.Insert(ALBUM_PREFIX + item.ID, item, null,
            DateTime.UtcNow.AddMinutes(5),
            System.Web.Caching.Cache.NoSlidingExpiration);
    }
    #endregion
}
